%{
#include <stdio.h>
#include <stdlib.h>
#include "latex_math.h"
#include "main.h"
#include "varTable.h"
#include <math.h>

int yyparse(void);
int yylex(void);

int eof = 0;

/* Last known syntactially correct line */
int last_good_line = 0;

/* Detects if an error is present */
int err = 0;

/* Counts the total number of erros */
int error_count = 0;

/* Makes sure solver-types aren't called twice without \answer*/
int solver_called = 0;

/* Used to keep track of variable names in \var commands */
char *tempString;

/* Error Handling Functions */

extern int lineno;

void yyerror(const char * message)
{
  if(err == 0)//report only the first error.
  {
	fprintf(stderr,"error   (line %d): %s\n", lineno,message);
	error_count++;
    err = 1;
  }
}

void yywarn(const char* message)
{
  if(err == 0)//report only the first error/warning
	fprintf(stderr, "warning (line %d): %s\n", lineno, message);
}

void check_solver()
{
  if(solver_called == 1)
	yywarn("solver called twice without \\answer");
  solver_called = 1;
}

void check_bounds(double variable, double min, double max, char* message)
{
  if(err == 0)
  {
	if(variable < min || variable > max)
	{
	  fprintf(stderr,"error   (line %d): %s not defined outside of [%g,%g]\n",lineno,message,min,max);
	  err =1;
	}
  }
}



int yywrap()
{
	return 1;
}

int main(int argc, char** argv)
{
		init(argc, argv);
		yyparse();
		cleanUp();

		if(error_count == 0)
		{
		  compile();
		  return 0;
		}else
		  return 1;
}

%}

%token <decimal> DIGIT
%token DECIMAL

%token NL T_EOF

%token OP_PLUS OP_MINUS
%token OP_TIMES OP_DIV
%token OP_EXP OP_FACT
%token OP_LPAREN OP_RPAREN OP_BAR
%token OP_LFLOOR OP_RFLOOR OP_LCEIL OP_RCEIL
%token OP_MIN OP_MAX
%token OP_MOD

%token OP_SIN OP_COS OP_TAN
%token OP_ARCSIN OP_ARCCOS OP_ARCTAN
%token OP_SEC OP_CSC OP_COT
%token OP_SINH OP_COSH OP_TANH OP_SECH OP_CSCH OP_COTH

%token OP_LOG OP_LN
%token OP_SQRT
%token OP_EQUAL

%token CONST_PI
%token CONST_PHI

%token L_LBRACE L_RBRACE L_VISIBLE_LBRACE L_VISIBLE_RBRACE
%token L_LBRACKET L_RBRACKET
%token L_FRAC L_BINOM
%token L_UNDER
%token L_BACKSLASH L_SEMI

%token M_COMMA

%token DEGREE

%type <decimal> expr0 expr expr1 expr2 expr2a expr2b expr3 group pgroup var2
%type <decimal> integer float degree fracint number
%type <decimal_list> multinomial_list
%type <string> variable sub_variable sub_variable1 sub_variable2

%token SOLVER ANSWER HIDDEN
%token VAR VAR_SOLVER VAR_HIDDEN
%token  <string> VARIABLE VAR_MODIFIER

%expect 4


%union
{
	int integer;
	double decimal;
	double *decimal_list;
	char *string;
}


%%


file		: file1 T_EOF {return 0;}
			| file1 error {printf("\n--- LaTeXCalc ABORTED ---\n"); 
			fprintf(stderr,"CRITICAL FAILURE: unexpected end of file.  Did you forget a closing brace '}' somewhere?  Maybe somewhere around line %d?\n", last_good_line); return 1;}
			;

file1		: file1 equation
			| file1 answer
			| file1 var
			|
			;

var		: VAR_SOLVER {last_good_line = lineno; } L_LBRACE variable {tempString = malloc(strlen($4) + 1); strcpy(tempString, $4);} 
				L_RBRACE {printf("\\ensuremath");} var2 
			| VAR {last_good_line = lineno; }L_LBRACE variable {tempString = malloc(strlen($4) + 1); strcpy(tempString, $4);} 
				L_RBRACE {printf("\\ensuremath{");} var2 {printf("%g}",$8);}
			| VAR_HIDDEN {last_good_line = lineno; }L_LBRACE variable {tempString = malloc(strlen($4) + 1); strcpy(tempString, $4);} L_RBRACE var2
			;

var2		: L_LBRACE expr0 L_RBRACE {addTableValue(tempString, $2); addTableError(tempString,err); 
												printTableErrors(&yyerror,&yywarn, tempString); free(tempString);
												$$ = $2; last_good_line=lineno;}
			| error L_RBRACE { $$ = 0;}
			;

equation : SOLVER {printf("\\ensuremath"); err = 0; check_solver(); last_good_line = lineno;} equation2
			| HIDDEN {err = 0; check_solver(); last_good_line=lineno;} equation2
			;

equation2 : L_LBRACE expr0 L_RBRACE {answ = $2; last_good_line = lineno;}
		  | error L_RBRACE 
		  ;

answer	: ANSWER {
        
        if(err == 0)
          printf("%g", answer());
        else
        {
          printf("error");
          err = 0;
        }
			solver_called = 0;
        }
		;

expr0		: expr {$$ = $1;}
				| expr OP_MOD L_LBRACE expr L_RBRACE {$$ = mod($1,$4);}
				;

expr	:	L_LBRACE expr L_RBRACE { $$ = $2; }
				| expr1 { $$ = $1; }
				| {yyerror("empty or invalid equation block");}
				;

expr1	: expr1 OP_PLUS expr2 { $$ = $1 + $3; }
		| expr1 OP_MINUS expr2 { $$ = $1 - $3; }
		| expr2 { $$ = $1; }
		;

expr2	: expr2 OP_TIMES expr2a { $$ = $1 * $3; }
		| expr2 expr2b { $$ = $1 * $2; }
		| expr2 OP_DIV expr2a { 
                $$ = 0;
                if($3 == 0)
                  yyerror("division by 0");
                else
				  $$ = $1 / $3; 
            }
		| expr2a
		;

expr2a : OP_MINUS expr2b { $$ = -$2; }
			 | expr2b {$$ = $1;}

expr2b : expr2b OP_EXP group { $$ = expo($1,$3); }
			 | expr3 OP_FACT { $$ = factorial($1); }
			 | expr3 { $$ = $1; }
			 ;

expr3 : OP_LPAREN expr OP_RPAREN { $$ = $2; }

			| OP_SQRT group
			{
				if($2 < 0)
				  yyerror("square root only works on nonnegitive numbers");
                else
				  $$ = sqrt($2);
			}
			| OP_SQRT L_LBRACKET expr L_RBRACKET group
			{
				if($3 <= 0)
				  yyerror("cannot take a nonpositive root");
				else if($5 < 0)
				  yyerror("negitive radicals not yet implemented");
				else
                  $$ = expo($5,1/$3);
			}
			| OP_SIN pgroup {$$ = sin_pi($2);}
			| OP_COS pgroup {$$ = cos_pi($2);}
			| OP_TAN pgroup {
			  double val_cos = cos_pi($2);
			  if(val_cos == 0)
				yyerror("tangent only exists when cosine is nonzero.");
			  else
				$$ = sin_pi($2)/val_cos;
			 }

			| OP_ARCSIN pgroup {check_bounds($2,-1,1,"arcsin"); $$ = asin($2);}
			| OP_ARCCOS pgroup {check_bounds($2,-1,1,"arccos"); $$ = acos($2);}
			| OP_ARCTAN pgroup {$$ = atan($2);}

			| OP_SEC pgroup {
			  double val_cos = cos_pi($2);
			  if(val_cos == 0)
				yyerror("secant only exists when cosine is nonzero.");
			  else
				$$ = 1/val_cos;}
			| OP_CSC pgroup {
			  double val_sin = sin_pi($2);
			  if(val_sin == 0)
				yyerror("cosecant only exists when sine is nonzero.");
			  else
				$$ = 1/val_sin;
			  }
			| OP_COT pgroup 
			{
			  double val_sin = sin_pi($2);
			  if(val_sin == 0)
				yyerror("cosecant only exists when sine is nonzero.");
			  else
				$$ = cos_pi($2)/val_sin;
			}

			| OP_SINH pgroup {$$ = (pow(E,$2) - pow(E,-$2))/2;}
			| OP_COSH pgroup {$$ = (pow(E,$2) + pow(E,-$2))/2;}
			| OP_SECH pgroup {$$ = 2/(pow(E,$2)+pow(E,-$2));}
			| OP_CSCH pgroup {
			  if($2 == 0)
				yyerror("hyperbolic cosecant must take a nonnegative argument");
			  else
				$$ = 2/(pow(E,$2)-pow(E,-$2));
			}
			| OP_TANH pgroup {$$ = (pow(E,2*$2) - 1)/(pow(E,2*$2) +1);}
			| OP_COTH pgroup {
			  if($2 == 0)
				yyerror("hyperbolic cotangent must take a nonnegative argument");
			  else 
				$$ = (pow(E,2*$2) + 1)/(pow(E,2*$2) -1);
			}

      | OP_LOG pgroup {
              if($2 <= 0)
                yyerror("log must take a positive argument");
              else
                $$ = log($2)/log(10);
            }
      | OP_LOG L_UNDER group pgroup {
              if($3 <= 0 || $4 <= 0)
                yyerror("log must take positive arguments");
              else
                $$ = log($4)/log($3);
            }
      | OP_LN pgroup {
              if($2 <= 0)
                yyerror("log must take a positive argument");
              else
                $$ = log($2);
             }
			| L_FRAC group group { 
                if($3 == 0)
                  yyerror("division by 0");
                else
                  $$ = $2/$3;
            }
	| OP_LFLOOR expr OP_RFLOOR
	{
	  $$ = floor($2);
	}
	| OP_LCEIL expr OP_RCEIL
	{
	  $$ = ceil($2);
	}
	| OP_MAX L_VISIBLE_LBRACE multinomial_list L_VISIBLE_RBRACE
	{
	  int count = $3[0];
	  if(count == 0)
		yyerror("max must take a nonempty set of numbers");
	  else
	  {
		double max = $3[1];
		for(int i = 2;i<=count;i++)
		  if($3[i] > max)
			max = $3[i];
		$$ = max;
	  }
	}
	| OP_MIN L_VISIBLE_LBRACE multinomial_list L_VISIBLE_RBRACE
	{
	  int count = $3[0];
	  if(count == 0)
		yyerror("min must take a nonempty set of numbers");
	  else
	  {
		double min = $3[1];
		for(int i = 2;i<=count;i++)
		  if($3[i] < min)
			min = $3[i];
		$$ = min;
	  }
	}

	| L_BINOM group group {$$ = binom($2,$3); }
	| L_BINOM group L_LBRACE multinomial_list L_RBRACE {
              
              $$ = (double)multinom($2,$4); }
    | number {$$ = $1;}
	| ANSWER {$$ = answ;}
	;

number      : float {$$ = $1; }
            | integer {$$ = $1;}
            | degree {$$ = $1;}
				| variable {$$ = getTableValue($1); printTableErrors(&yyerror,&yywarn, $1); }
            ;

variable	: VARIABLE {$$ = $1;}
			| VAR_MODIFIER L_LBRACE variable L_RBRACE
			{
			  $$ = (char*)malloc(sizeof(char)*(strlen($1)+strlen($3)+1));
			  sprintf($$,"\\%s{%s}",$1,$3);
			}
			| VARIABLE L_UNDER sub_variable	{
				$$ = (char*)malloc(sizeof(char)*(strlen($1)+strlen($3)+1));
			  sprintf($$,"%s_%s",$1,$3);
			  }
			;

sub_variable	: L_LBRACE sub_variable1 L_RBRACE	{ $$ = $2;}
					| L_LBRACE VARIABLE L_RBRACE			{ $$ = $2;}
					| sub_variable2 							{ $$ = $1;}
					;


sub_variable2	: VARIABLE	{$$ = $1;}
					| DIGIT		{$$ = malloc(sizeof(char));
									sprintf($$, "%d", (int)$1);
									}
					;

sub_variable1	: integer	{
										char temp[40];
										sprintf(temp, "%d", (int)$1);
										int length = strlen(temp);
										$$ = malloc(sizeof(char)*length);
										strcpy($$, temp);
									}
					| float		{
										char temp[40];
										sprintf(temp, "%g", $1);
										int length = strlen(temp);
										$$ = malloc(sizeof(char)*length);
										strcpy($$, temp);
									}
					;

float      :   integer DECIMAL { $$ = $1; }
           |   integer DECIMAL fracint { $$ = $1 + $3; }
           |   DECIMAL fracint  {$$ = $2;}
           ;

fracint     :  DIGIT fracint {$$ = $1/10 + $2/10;}
            |  DIGIT    {$$ = $1/10;}
  

integer	    : integer DIGIT	{ $$ = 10*$1 + $2; }
						| DIGIT
						;

degree		: integer DEGREE {$$ = $1 * (PI / 180.0);}
			| float DEGREE {$$ = $1 * (PI / 180.0);}
			;


group		: L_LBRACE expr L_RBRACE { $$ = $2; }
            | DIGIT { $$ = $1; }
			| variable {$$ = getTableValue($1); printTableErrors(&yyerror,&yywarn, $1); }
			;

pgroup		: L_LBRACE expr L_RBRACE { $$ = $2; }
            | OP_LPAREN expr OP_RPAREN { $$ = $2; }
			;

multinomial_list	: multinomial_list M_COMMA expr
									{
										int size = $1[0] + 1;
										double* list = (double*)malloc(sizeof(double)*(size+1));
										for(int i = 0;i<size;i++)
											list[i] = $1[i];
										list[0]++;
										list[size] = $3;
										$$ = list;
										free($1);
									}
									| expr M_COMMA expr 
									{
										double* list = (double*)malloc(sizeof(double)*3);
										list[0] = 2;
										list[1] = $1;
										list[2] = $3;
										$$ = list;
									}
					 | error { double* list = (double*)malloc(sizeof(double)*2); list[0] = 0; $$=list;}
									;

%%
